#pragma once

#include <cstddef>
#include <map>
#include <memory>

#include "common/macros.h"
extern "C" {
#include <postgres.h>

#include <nodes/memnodes.h>
#include <utils/palloc.h>
}

namespace optimizer {

struct MemoryContextSwitch {
  DISALLOW_COPY_AND_MOVE(MemoryContextSwitch);

  MemoryContextData *old_context_;
  explicit MemoryContextSwitch(MemoryContextData *ctx) { old_context_ = MemoryContextSwitchTo(ctx); }

  ~MemoryContextSwitch() { MemoryContextSwitchTo(old_context_); }
};

#define ToTopMemoryContext()           \
  optimizer::MemoryContextSwitch ctx { \
    TopMemoryContext                   \
  }

struct MemoryTracer {
  struct AllocInfo {
    size_t size_;
    void *call_;
  };

  std::map<void *, AllocInfo> allocated_memory_;

  bool enable_{true};

  static auto &Instance() {
    static MemoryTracer instance;
    return instance;
  }

  void Decrese(void *ptr);

  void Reset();

  size_t Check();
};

struct MemoryCounter {
  std::size_t nblocks_{0};    /* Total number of malloc blocks */
  std::size_t freechunks_{0}; /* Total number of free chunks */
  std::size_t totalspace_{0}; /* Total bytes requested from malloc */
  std::size_t freespace_{0};  /* The unused portion of totalspace */

  void Count();
};

struct EnableGuard {
  bool was_enable_{false};

  EnableGuard() {
    was_enable_ = optimizer::MemoryTracer::Instance().enable_;
    optimizer::MemoryTracer::Instance().enable_ = false;
  }

  explicit operator bool() const { return was_enable_; }

  ~EnableGuard() { optimizer::MemoryTracer::Instance().enable_ = was_enable_; }
};

inline void AddCheck(void *ptr, size_t bytes) {
  if constexpr (PLAN_DEBUG) {
    EnableGuard gad;
    if (gad) {
      MemoryTracer::Instance().allocated_memory_.insert({ptr, {bytes, __builtin_return_address(0)}});
    }
  }
}

inline void DeleCheck(void *ptr) {
  if constexpr (PLAN_DEBUG) {
    EnableGuard gad;
    if (gad) {
      MemoryTracer::Instance().Decrese(ptr);
    }
  }
}

struct Deleter {
  void operator()(void *ptr) const { pfree(ptr); }
};

struct EmptyDeleter {
  void operator()(void *ptr) const {}
};

template <typename T>
std::shared_ptr<T> share_from_pg(T *ptr) {  // NOLINT
  return std::shared_ptr<T>(ptr, EmptyDeleter());
}

template <typename T>
std::shared_ptr<T> share_from_cpp(T *ptr) {  // NOLINT
  return std::shared_ptr<T>(ptr, Deleter());
}

template <typename T>
std::unique_ptr<T, Deleter> unique_from_pg(T *ptr) {  // NOLINT
  return std::unique_ptr<T, Deleter>(ptr);
}

template <typename T>
std::unique_ptr<T, EmptyDeleter> unique_from_cpp(T *ptr) {  // NOLINT
  return std::unique_ptr<T, EmptyDeleter>(ptr);
}

}  // namespace optimizer